/*
 * main.c
 *
 *  Created on: 24/apr/2013
 *      Author: Ialenti Andrea, Marangon Fabio
 */

#include "../headers/Callback.h"
#include "../headers/CPLEXProblemGenerator.h"
#include "../headers/CPLEXProblemSolver.h"
#include "../headers/Graph.h"
#include "../headers/Heuristic.h"
#include "../headers/Lagrangian.h"
#include "../headers/Printer.h"
#include "../headers/StatusCodes.h"
#include "../headers/Tester.h"
#include "../headers/Types.h"
#include "../headers/Utility.h"

#include "../lib/concorde/concorde.h"

#include <string.h>
#include <time.h>

#include <ilcplex/cplex.h>

void printHeader();

int main(int argc, char **argv) {
	char *funcName = initCharArray(5);
	sprintf(funcName, "main");

	//	Import program configuration parameters from file.
	Config cfg;
	initCfgData(&cfg, "files/config.cfg");

	//	Initialize random number generator.
	srand (time(NULL));

	Status status;

	//	Variables passed as command line arguments.
	bool verbose, nodePerm, use3Opt, useCPLEX, useCB, useProxSrch, lagrOpt = false;
	char *fileName = initCharArray(cfg.NAME_LEN);

	//	CPLEX variables.
	CPXENVptr env = NULL;
	CPXLPptr lp = NULL, psLP = NULL;

	//	Other variables.
	Graph g;
	Problem p, psP;
	cbData data;
	double LB = 0, UB = cfg.INF, execTime, normTime;
	OneTree lagrTree, heuTour;
	time_t t1, t2;

	printHeader();
	fprintf(stdout, "\n\n'%s': program started. . .\n", funcName);
	time(&t1);

	//	Read command line arguments.
	fprintf(stdout, "'%s': parsing command line parameters. . .\n", funcName);
	parseCommandLine(&cfg, argc, argv, &verbose, fileName, &nodePerm, &use3Opt, &useProxSrch, &useCPLEX, &useCB);

	//	Import the TSP-lib problem.
	fprintf(stdout, "\n'%s': importing TSP lib problem. . .\n", funcName);
	status = importProblem(&cfg, fileName, &g, verbose, nodePerm);
	if (status != SUCCESS) {
		fprintf(stdout, "***'%s': error (%d) importing the graph, program will terminate. . .\n", funcName, status);
		freeCharArray(funcName);
		freeCharArray(fileName);
		return ERR_MAIN;
	}

	//	Apply lagrangian relaxation to get lower and upper bounds for the problem (then restore original costs).
	fprintf(stdout, "\n'%s': simplifying problem. . .\n", funcName);
	status = simplifyProblem(&cfg, &g, &LB, &UB, &lagrTree, &heuTour, use3Opt, &lagrOpt, verbose);
	if (status != SUCCESS) {
		fprintf(stdout, "***'%s': error finding lower and upper bounds, program will terminate. . .\n", funcName);
		freeCharArray(funcName);
		freeCharArray(fileName);
		return ERR_MAIN;
	}
	else {
		fprintf(stdout, "'%s': problem simplified. . .\n\n", funcName);

		//	Solve the TSP problem using the proximity search method.
		if (useProxSrch) {
			fprintf(stdout, "\n'%s': solving TSP problem using the proximity search setup. . .\n\n", funcName);

			//	Create the initial proximity search and the normal problems.
			status = createProxySearchProblem(&cfg, &env, &psLP, &lp, &p, &psP, &g, LB, UB, &heuTour, verbose);
			if (status != SUCCESS) {
				fprintf(stdout, "***'%s': error creating the initial PS and normal problems, program will terminate. . .\n", funcName);
				freeCharArray(funcName);
				freeCharArray(fileName);
				disposeOneTree(&lagrTree);
				disposeOneTree(&heuTour);
				return ERR_MAIN;
			}

			//	Add lagrangian SECs to the TSP problem.
			fprintf(stdout, "\n'%s': adding lagrangian SECs to the original TSP problem. . .\n", funcName);
			status = addLagrangianSECs(&cfg, env, lp, &p, &lagrTree);
			if (status != SUCCESS) {
				fprintf(stdout, "***'%s': error (%d) adding lagrangian SECs, program will terminate. . .\n", funcName, getStatusCode(status));
				freeCharArray(funcName);
				freeCharArray(fileName);
				disposeOneTree(&lagrTree);
				disposeOneTree(&heuTour);
				return ERR_MAIN;
			}

			//	Solve the TSP problem using the proximity search method.
			int globalTimeLimit = cfg.CPX_PS_GTL;
			if (p.nodes > 400)
				globalTimeLimit *= 6;
			else if (p.nodes > 200)
				globalTimeLimit *= 4;
			else if (p.nodes > 100)
				globalTimeLimit *= 2;
			fprintf(stdout, "'%s': starting the proximity search algorithm with global time limit: %0.2f hour(s). . .\n\n", funcName, (double)globalTimeLimit / 3600);
			status = proxySearch(&cfg, env, lp, psLP, &p, &psP, &g, verbose);
			if (status != SUCCESS) {
				fprintf(stdout, "***'%s': error (%d) solving the TSP problem using the proximity search method, program will terminate. . .\n", funcName, getStatusCode(status));
				freeCharArray(funcName);
				freeCharArray(fileName);
				disposeOneTree(&lagrTree);
				disposeOneTree(&heuTour);
				return ERR_MAIN;
			}
		} else {

			//	Activate problem solving using CPLEX.
			if (useCPLEX) {

				//	Create the initial relaxed problem, without the Sub-tour Elimination Constraints (SECs).
				status = createCPLEXProblem(&cfg, &env, &lp, &p, &g, LB, UB, verbose, useCB);
				if (status != SUCCESS) {
					fprintf(stdout, "***'%s': error (%d) creating the initial problem, program will terminate. . .\n", funcName, getStatusCode(status));
					freeCharArray(funcName);
					freeCharArray(fileName);
					disposeOneTree(&lagrTree);
					disposeOneTree(&heuTour);
					return ERR_MAIN;
				}

				//	Add lagrangian SECs to the TSP problem.
				status = addLagrangianSECs(&cfg, env, lp, &p, &lagrTree);
				if (status != SUCCESS) {
					fprintf(stdout, "***'%s': error (%d) adding lagrangian SECs, program will terminate. . .\n", funcName, getStatusCode(status));
					freeCharArray(funcName);
					freeCharArray(fileName);
					disposeOneTree(&lagrTree);
					disposeOneTree(&heuTour);
					return ERR_MAIN;
				}

				//	Activate user-defined CPLEX callbacks.
				if (useCB) {

					//	Optimize the problem to obtain a first solution and plot it.
					status = solveTSP_CB(&cfg, &env, &lp, &p, &g, &data, verbose);
					if (status != SUCCESS) {
						fprintf(stdout, "***'%s': error (%d) finding the first solution, program will terminate. . .\n", funcName, getStatusCode(status));
						freeCharArray(funcName);
						freeCharArray(fileName);
						disposeOneTree(&lagrTree);
						disposeOneTree(&heuTour);
						return ERR_MAIN;
					}
				}

				//	Do not use CPLEX callbacks: solve the problem adding SECs iteratively (Miliotis method).
				else {
					status = solveUsingMiliotis(&cfg, &env, &lp, &p, &g, verbose);
					if (status != SUCCESS) {
						fprintf(stdout, "***'%s': error (%d) solving the TSP problem, program will terminate. . .\n", funcName, getStatusCode(status));
						freeCharArray(funcName);
						freeCharArray(fileName);
						disposeOneTree(&lagrTree);
						disposeOneTree(&heuTour);
						return ERR_MAIN;
					}
				}
			}

			//	Run the our branch and bound algorithm on the pruned graph without using CPLEX.
			else {

				//	If it was chosen to use our branch and bound, ignore the optimal tour found during the lagrangian relaxation and run the algorithm on the pruned graph.
				if (lagrOpt)
					fprintf(stdout, "'%s': program will now ignore the lagrangian results and run our branch and bound algorithm on the pruned graph (%d edges). . .\n", funcName, g.edgeCnt);

				UB++;
				status = runBranchAndBound(&cfg, &g, &UB, verbose);
				if (status != SUCCESS) {
					fprintf(stdout, "***'%s': error (%d) running branch and bound algorithm, program will terminate. . .\n", funcName, getStatusCode(status));
					freeCharArray(funcName);
					freeCharArray(fileName);
					disposeOneTree(&lagrTree);
					disposeOneTree(&heuTour);
					return ERR_MAIN;
				}
			}
		}
	}

	//	Release the memory and quit.
	freeCharArray(fileName);
	disposeOneTree(&lagrTree);
	disposeOneTree(&heuTour);
	status = releaseMemory(&cfg, &env, &lp, &p, &g, lagrOpt, useProxSrch, &psLP, &psP);
	if (status != SUCCESS) {
		fprintf(stdout, "***'%s': error (%d) releasing the memory, program will terminate. . .\n", funcName, status);
		freeCharArray(funcName);
		return ERR_MAIN;
	}

	//	Print total execution time.
	time(&t2);
	execTime = difftime(t2, t1);
	normTime = execTime * cfg.NORM_TIME;
	fprintf(stdout, "\n'%s': program terminated successfully in %0.2f second(s) (%0.2f second(s) normalized time).\n\n", funcName, execTime, normTime);
	freeCharArray(funcName);
	return SUCCESS;
}

void printHeader() {
	fprintf(stdout, "\t#######################################################\n");
	fprintf(stdout, "\t#                                                     #\n");
	fprintf(stdout, "\t#                 University of Padua                 #\n");
	fprintf(stdout, "\t#        Department of Information Engineering        #\n");
	fprintf(stdout, "\t#                                                     #\n");
	fprintf(stdout, "\t#                TSP problem solver by                #\n");
	fprintf(stdout, "\t#             Ialenti Andrea, ID: 1056894             #\n");
	fprintf(stdout, "\t#             Marangon Fabio, ID: 1035686             #\n");
	fprintf(stdout, "\t#                                                     #\n");
	fprintf(stdout, "\t#             Professor: Fischetti Matteo             #\n");
	fprintf(stdout, "\t#                                                     #\n");
	fprintf(stdout, "\t#           Version: 1.0   (27 - 11 - 2013)           #\n");
	fprintf(stdout, "\t#                                                     #\n");
	fprintf(stdout, "\t#######################################################");
	return;
}
